package io.github.vrivotti.unifiedcircularprogress;

import io.github.vrivotti.unifiedcircularprogress.util.AnimatorValueExt;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;

/**
 * 动画类
 *
 * @author name
 * @since 2021-06-15
 */
public class ProgressBarExt extends Component implements Component.DrawTask {
    private static final float ANGULAR_EPSILON = 1 / 3600f;
    private int mWidthrate = 10;
    private int mDuration = 1333;
    private RectFloat mRect;
    private Paint mPaint;
    private Arc mArc;
    private AnimatorValueExt startAnimatorValue;
    private AnimatorValueExt endAnimatorValue;
    private float mProgress = 0f;
    private boolean mIndeterminate = false;
    private boolean mShouldStartAnimationDrawable = false;
    private float ringStart = 0;
    private float ringEnd = 0;
    private int defaultColor = 0xFF000000;

    /**
     * 构造方法
     *
     * @param context 上下文
     */
    public ProgressBarExt(Context context) {
        super(context);
        init();
    }

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param attrSet 内容
     */
    public ProgressBarExt(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init();
    }

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param attrSet 内容
     * @param styleName 样式
     */
    public ProgressBarExt(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init();
    }

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param attrSet 内容
     * @param resId 资源ID
     */
    public ProgressBarExt(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
        init();
    }

    /**
     * 初始化
     */
    private void init() {
        mPaint = new Paint();
        mArc = new Arc();
        mPaint.setColor(new Color(defaultColor));
        mPaint.setStyle(Paint.Style.STROKE_STYLE);
        this.addDrawTask(this::onDraw);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (mRect == null) {
            int strokeWidth = component.getWidth() / mWidthrate;
            mRect = new RectFloat(strokeWidth,
                    strokeWidth,
                    component.getWidth() - strokeWidth,
                    component.getHeight() - strokeWidth);
            mPaint.setStrokeWidth(strokeWidth);
        }

        drawCircleAnim(canvas);
    }

    /**
     * 设置进度
     *
     * @param progress 进度值
     */
    public void setProgress(int progress) {
        int valueProgress = 0;
        valueProgress = constrain(progress, 0, 100);
        int range = 100;
        final float scale = range > 0 ? (valueProgress - 0) / (float) range : 0;
        if (scale == mProgress && !mIndeterminate) {
            return;
        }
        setProgress(scale);
    }

    /**
     * 设置进度
     *
     * @param progress 进度值
     */
    public void setProgress(float progress) {
        mProgress = progress;
        mIndeterminate = false;
        setupDeterminateAnimators();
        startAnimation();
    }

    /**
     * setIndeterminate
     *
     * @param indeterminate 布尔值
     */
    public void setIndeterminate(boolean indeterminate) {
        if (!indeterminate) {
            setProgress(mProgress);
            return;
        }
        if (!mIndeterminate) {
            mIndeterminate = true;
            reduceAngles();
            if (ringStart < ANGULAR_EPSILON) {
                setupIndeterminateAnimators();
            }
        }
        startAnimation();
    }

    private void setupIndeterminateAnimators() {
        if (ringEnd - ringStart <= 0.5f) {
            float base = ringStart < ANGULAR_EPSILON ? 0 : ringStart;
            setupAnimators(
                    new float[]{ringStart, base + 0.2f, base + 0.8f, base + 1.2f},
                    new float[]{0, 0.5f, 0.7f, 1.0f},
                    new float[]{ringEnd, base + 0.65f, base + 1.05f, base + 1.25f},
                    new float[]{0, 0.2f, 0.5f, 1.0f},
                    mDuration);
        } else {
            float next = (float) Math.ceil(ringEnd);
            float timeToReset = next - ringStart;
            setupAnimators(
                    new float[]{ringStart, next},
                    new float[]{0, 1},
                    new float[]{ringEnd, next + 0.05f},
                    new float[]{0, 1},
                    (long) (mDuration * timeToReset));
        }
    }

    private void setupDeterminateAnimators() {
        reduceAngles();
        if (ringStart < ANGULAR_EPSILON && ringEnd <= mProgress) {
            setupAnimators(
                    new float[]{ringStart, 0},
                    new float[]{0, 1},
                    new float[]{ringEnd, mProgress},
                    new float[]{0, 1},
                    (long) (mDuration * (mProgress - ringEnd)));
        } else {
            float next = (float) Math.ceil(ringEnd);
            float timeToReset = next - ringStart;
            float timeFraction = timeToReset / (timeToReset + mProgress);
            if (timeFraction > 0.99f) {
                timeFraction = 0.99f;
            }

            setupAnimators(
                    new float[]{ringStart, next, next},
                    new float[]{0, timeFraction, 1},
                    new float[]{ringEnd, next, next + mProgress},
                    new float[]{0, timeFraction, 1},
                    (long) (mDuration * (timeToReset + mProgress)));
        }
    }

    private void setupAnimators(float[] startValues, float[] startRate, float[] endValues,
                                float[] endRate, long duration) {
        cleanUpAnimator(startAnimatorValue);
        cleanUpAnimator(endAnimatorValue);

        startAnimatorValue = AnimatorValueExt.ofFloat(startRate, startValues);
        startAnimatorValue.setDuration(duration);
        startAnimatorValue.setValueUpdateListener(new AnimatorValueExt.ValueUpdateListener() {
            @Override
            public void onAnimationUpdate(AnimatorValueExt animatorValue) {
                ringStart = animatorValue.getAnimatedValue();
                invalidate();
            }
        });
        startAnimatorValue.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
            }

            @Override
            public void onStop(Animator animator) {
            }

            @Override
            public void onCancel(Animator animator) {
            }

            @Override
            public void onEnd(Animator animator) {
                if (mIndeterminate) {
                    setupIndeterminateAnimators();
                    startAnimatorValue.start();
                    endAnimatorValue.start();
                }
            }

            @Override
            public void onPause(Animator animator) {
            }

            @Override
            public void onResume(Animator animator) {
            }
        });

        endAnimatorValue = AnimatorValueExt.ofFloat(endRate, endValues);
        endAnimatorValue.setDuration(duration);
        endAnimatorValue.setValueUpdateListener(new AnimatorValueExt.ValueUpdateListener() {
            @Override
            public void onAnimationUpdate(AnimatorValueExt animatorValue) {
                ringEnd = animatorValue.getAnimatedValue();
                invalidate();
            }
        });
    }

    private void reduceAngles() {
        if (ringEnd < ringStart) {
            ringEnd = ringStart;
        }
        if (ringEnd > ringStart + 1) {
            ringEnd = ringStart + 1;
        }
        if (ringStart >= 1 || ringStart < 0) {
            double ff = Math.floor(ringStart);
            ringStart = (float) (ringStart - ff);
            ringEnd = (float) (ringEnd - ff);
        }
    }

    private void cleanUpAnimator(AnimatorValue animator) {
        if (animator != null) {
            animator.stop();
        }
    }

    private void startAnimation() {
        mShouldStartAnimationDrawable = true;
        invalidate();
    }

    private void start() {
        if (startAnimatorValue.isRunning()) {
            return;
        }
        startAnimatorValue.start();
        endAnimatorValue.start();
    }

    private static int constrain(int amount, int low, int high) {
        return amount < low ? low : (amount > high ? high : amount);
    }

    private void drawCircleAnim(Canvas canvas) {
        float startAngle = 360 * ringStart - 90;
        float sweepAngle = 360 * (ringEnd - ringStart);
        mArc.setArc(startAngle, sweepAngle, false);
        canvas.drawArc(mRect, mArc, mPaint);
        if (mShouldStartAnimationDrawable) {
            mShouldStartAnimationDrawable = false;
            start();
        }
    }
}
